/*
 * Copyright (C) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "border_option_napi.h"
#include "imageKnife_internal.h"
#include "napi/native_api.h"
#include "imageknife_option_napi.h"
#include "hilog/log.h"
#include "imageknife.h"
#include "onload_callback_napi.h"
#include <cstdint>

namespace ImageKnifeC {

std::multimap<std::string, std::string> &GetGlobaHttpHeaders()
{
    static std::multimap<std::string, std::string> httpHeaders;
    return httpHeaders;
}

void PraseHttpHeaders(napi_env env, napi_value jsHeaders, std::multimap<std::string, std::string> &headers)
{
    const uint32_t maxKeyLength = 128;
    const uint32_t maxValueLength = 256;
    uint32_t length = 0;
    napi_status status = napi_get_array_length(env, jsHeaders, &length);
    if (status != napi_ok) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN,
                     "ImageKnifeOption::PraseHttpHeaders",
                     "PraseHttpHeaders:napi_get_array_length failed");
        return;
    }

    char keyBuffer[maxKeyLength] = {0};
    size_t keyLen = 0;
    char valueBuffer[maxValueLength] = {0};
    size_t valueLen = 0;
    for (int i = 0; i < length; i++) {
        napi_handle_scope scope;
        napi_value element;
        napi_value key;
        napi_value value;
        napi_valuetype type;
        napi_open_handle_scope(env, &scope);
        napi_get_element(env, jsHeaders, i, &element);
        napi_get_named_property(env, element, "key", &key);
        napi_get_value_string_utf8(env, key, keyBuffer, sizeof(keyBuffer), &keyLen);
        keyBuffer[keyLen] = 0;

        napi_get_named_property(env, element, "value", &value);
        napi_typeof(env, value, &type);
        if (type == napi_string) {
            napi_get_value_string_utf8(env, value, valueBuffer, sizeof(valueBuffer), &valueLen);
            valueBuffer[valueLen] = 0;
            headers.insert(std::make_pair(std::string(keyBuffer), std::string(valueBuffer)));
        } else if (type == napi_object) {
            uint32_t valueArrayLength = 0;
            napi_get_array_length(env, value, &valueArrayLength);

            for (int j = 0; j < valueArrayLength; j++) {
                napi_value valueElement;
                napi_valuetype valueType;
                napi_get_element(env, value, j, &valueElement);
                napi_typeof(env, valueElement, &valueType);
                if (valueType != napi_string) {
                    OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN,
                                 "ImageKnifeOption::PraseHttpHeaders",
                                 "value type is error");
                    break;
                }
                napi_get_value_string_utf8(env, valueElement, valueBuffer, sizeof(valueBuffer), &valueLen);
                valueBuffer[valueLen] = 0;
                headers.insert(std::make_pair(std::string(keyBuffer), std::string(valueBuffer)));
            }
        } else {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN,
                         "ImageKnifeOption::PraseHttpHeaders",
                         "parse type error : %{public}d",
                         type);
        }
        napi_close_handle_scope(env, scope);
    }
}

ImageKnifeOptionNapi::ImageKnifeOptionNapi(napi_env env, napi_value &jsObject)
{
    env_ = env;
    napi_create_reference(env, jsObject, 1, &jsThis_);
    imageLoader =  ImageKnifeInternal::GetInstance().GetDefaultImageKnifeLoader();

    napi_value value;
    if (napi_get_named_property(env, jsObject, "loadSrc", &value) == napi_ok) {
        loadSrc.SetValue(env, value);
    }

    if (napi_get_named_property(env, jsObject, "placeholderSrc", &value) == napi_ok) {
        placeholderSrc.SetValue(env, value);
    }

    if (napi_get_named_property(env, jsObject, "errorholderSrc", &value) == napi_ok) {
        errorholderSrc.SetValue(env, value);
    }

    if (napi_get_named_property(env, jsObject, "signature", &value) == napi_ok) {
        napi_valuetype type;
        napi_status status = napi_typeof(env, value, &type);
        if (type == napi_string) {
            size_t length;
            status = napi_get_value_string_utf8(env, value, nullptr, 0, &length);
            char buf[length + 1];
            status = napi_get_value_string_utf8(env, value, buf, length + 1, nullptr);
            signature = buf;
            OH_LOG_Print(LOG_APP, LOG_DEBUG, LOG_DOMAIN,
                         "ImageKnifeOption::ImageKnifeOption",
                         "signature is : %{public}s",
                         signature.c_str());
        }
    }

    if (napi_get_named_property(env, jsObject, "priority", &value) == napi_ok) {
        napi_valuetype type;
        napi_status status = napi_typeof(env, value, &type);
        if (type == napi_number) {
            int32_t priorityValue = 0;

            // 获取整数值
            status = napi_get_value_int32(env, value, &priorityValue);
            if (status == napi_ok) {
                priority = priorityValue;
                OH_LOG_Print(LOG_APP, LOG_DEBUG, LOG_DOMAIN,
                             "ImageKnifeOption::ImageKnifeOption",
                             "priority is : %{public}d",
                             priorityValue);
            } else {
                OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN,
                             "ImageKnifeOption::ImageKnifeOption",
                             "Failed to get priority value as number.");
            }
        }
    }

    if (napi_get_named_property(env, jsObject, "onLoadListener", &value) == napi_ok) {
        onLoadListener = std::make_shared<OnLoadCallBackNapi>(env, value);
    }

    if (napi_get_named_property(env, jsObject, "objectFit", &value) == napi_ok) {
        objectFit = ParseImagFit(env, value);
    }

    if (napi_get_named_property(env, jsObject, "placeholderObjectFit", &value) == napi_ok) {
        placeholderObjectFit = ParseImagFit(env, value);
    }

    if (napi_get_named_property(env, jsObject, "errorholderObjectFit", &value) == napi_ok) {
        errorholderObjectFit = ParseImagFit(env, value);
    }

    if (napi_get_named_property(env, jsObject, "border", &value) == napi_ok) {
        if (border == nullptr) {
            border = new BorderOptionNapi;
        }
        auto borderNapi = reinterpret_cast<BorderOptionNapi*>(border);
        borderNapi->SetBorderOption(env, value);
    }

    if (napi_get_named_property(env, jsObject, "onComplete", &value) == napi_ok) {
        napi_valuetype type;
        napi_typeof(env, value, &type);
        if (type != napi_undefined) {
            napi_create_reference(env, value, 1, &onComplete_);
            onComplete = [this](EventImage eventImage) {
                this->CallJsOnComplete(eventImage);
            };
        }
    }

    bool isExist = false;
    napi_has_named_property(env, jsObject, "headerOption", &isExist);
    if (isExist) {
        napi_get_named_property(env, jsObject, "headerOption", &value);
        PraseHttpHeaders(env, value, httpHeaders);
    }

    napi_has_named_property(env, jsObject, "progressListener", &isExist);
    if (isExist) {
        napi_get_named_property(env, jsObject, "progressListener", &value);
        ParseProgressListener(env, value);
    }
}

void ImageKnifeOptionNapi::ParseContext(napi_env env, napi_value value)
{
    bool hasProperty = false;
    napi_status status = napi_has_named_property(env, value, "resourceManager", &hasProperty);
    if (status == napi_ok && hasProperty) {
        napi_value jsResMgr;
        status = napi_get_named_property(env, value, "resourceManager", &jsResMgr);
        if (status == napi_ok) {
            context.resourceManager = OH_ResourceManager_InitNativeResourceManager(env, jsResMgr);
        }
    }
}

void ImageKnifeOptionNapi::ParseProgressListener(napi_env env, napi_value &value)
{
    napi_status status;
    napi_valuetype type;
    napi_typeof(env, value, &type);
    if (type != napi_undefined) {
        napi_value workName;
        napi_create_string_utf8(env, "ImageKnife Napi progressListener", NAPI_AUTO_LENGTH, &workName);

        auto func = [](napi_env env, napi_value jsCallback, void *context, void *data) {
            napi_value number;
            double *progress = static_cast<double *>(data);
            napi_create_double(env, *progress, &number);
            napi_call_function(env, nullptr, jsCallback, 1, &number, nullptr);
            delete progress;
        };

        status = napi_create_threadsafe_function(env, value, nullptr, workName, 0, 1,
                                                 nullptr, nullptr, nullptr, func, &progressThreadSafeFunc_);
        if (status == napi_ok) {
            napi_create_reference(env, value, 1, &progressListener_);
            progressListener = [this](double progress) {
                this->CallJsProgressListener(progress);
            };
        } else {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "ImageKnifeOption::ParseProgressListener",
                         "Failed to napi_create_threadsafe_function status:%{public}d", status);
        }
    }
}

ArkUI_ObjectFit ImageKnifeOptionNapi::ParseImagFit(napi_env env, napi_value &value)
{
    // 默认为Contain与js版本的ImageKnife保持一致，与系统Image不一致
    int num = ARKUI_OBJECT_FIT_CONTAIN;
    napi_get_value_int32(env, value, &num);
    // js 侧的枚举 ImageFit 与侧对应的ArkUI_ObjectFit 不一致，需要映射
    ArkUI_ObjectFit jsEnum[] = {
        ARKUI_OBJECT_FIT_FILL,  // js侧的该枚举值为0，而不是4
        ARKUI_OBJECT_FIT_CONTAIN,
        ARKUI_OBJECT_FIT_COVER,
        ARKUI_OBJECT_FIT_AUTO,
        ARKUI_OBJECT_FIT_CONTAIN, // js侧的枚举值没有4，为异常值使用默认Contain
        ARKUI_OBJECT_FIT_NONE,  // js侧的该枚举值为5，而不是6
        ARKUI_OBJECT_FIT_SCALE_DOWN // js侧的该枚举值为6，而不是5
    };

    // js后续的枚举值从TOP_START 开始显式定义枚举值, 后续的枚举值比C侧大1
    if (num > sizeof(jsEnum) / sizeof(jsEnum[0])) {
        return static_cast<ArkUI_ObjectFit>(num - 1);
    } else {
        return jsEnum[num];
    }
}

void ImageKnifeOptionNapi::CallJsProgressListener(double &progress)
{
    napi_status status = napi_call_threadsafe_function(progressThreadSafeFunc_,
                                                       new double(progress), napi_tsfn_blocking);
    if (status != napi_ok) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "ImageKnifeOption::CallJsProgressListener",
                     "CallJsProgressListener failed, napi status: %{public}d", status);
    }
}

void ImageKnifeOptionNapi::CallJsOnComplete(EventImage &eventImage)
{
    napi_status status;
    napi_value thisValue;
    napi_value jsFunc;
    napi_value eventImageValue;
    napi_value number;

    napi_create_object(env_, &eventImageValue);

    napi_create_int32(env_, eventImage.loadingStatus, &number);
    napi_set_named_property(env_, eventImageValue, "loadingStatus", number);

    napi_create_double(env_, eventImage.width, &number);
    napi_set_named_property(env_, eventImageValue, "width", number);

    napi_create_double(env_, eventImage.height, &number);
    napi_set_named_property(env_, eventImageValue, "height", number);

    napi_create_double(env_, eventImage.componentWidth, &number);
    napi_set_named_property(env_, eventImageValue, "componentWidth", number);

    napi_create_double(env_, eventImage.componentHeight, &number);
    napi_set_named_property(env_, eventImageValue, "componentHeight", number);

    napi_create_double(env_, eventImage.contentOffsetX, &number);
    napi_set_named_property(env_, eventImageValue, "contentOffsetX", number);

    napi_create_double(env_, eventImage.contentOffsetY, &number);
    napi_set_named_property(env_, eventImageValue, "contentOffsetY", number);

    napi_create_double(env_, eventImage.contentWidth, &number);
    napi_set_named_property(env_, eventImageValue, "contentWidth", number);

    napi_create_double(env_, eventImage.contentHeight, &number);
    napi_set_named_property(env_, eventImageValue, "contentHeight", number);

    napi_get_reference_value(env_, jsThis_, &thisValue);
    napi_get_reference_value(env_, onComplete_, &jsFunc);
    status = napi_call_function(env_, thisValue, jsFunc, 1, &eventImageValue, nullptr);
    if (status != napi_ok) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN,
                     "ImageKnifeOption",
                     "CallJsOnComplete failed, napi status: %{public}d",
                     status);
    }
}

} // end of namespace